﻿\subsection{物理内存管理}

引导加载器在引导处理器进入保护模式之前，通过调用 \lstinline{0x15} 号 BIOS 中断获取每段物理内存的地址范围描述符，操作系统内核就是通过该数据结构获知物理内存信息，对物理内存空间的感知是操作系统实现内存管理的基础。操作系统为可用的物理内存空间建立物理内存池，以物理页框为单位对物理内存进行管理。

地址范围描述符定义为如下的结构体。在结构体之前的宏定义 \lstinline{MEMORY_ADDRESS_MAP_BASE} 中存放的是内存地址图的基址，这是在分页方案中预设好的。由于本项目的操作系统工作于 32 位环境中，因此在实际使用下述结构体时将忽略地址范围描述符中的高 32 位字段。

\begin{minted}[breaklines, breakautoindent=true]{c}
typedef struct __AddressRangeDescriptor
{
    DWORD_PTR dwlpBaseAddrLow; /* 低 32 位地址 */
    DWORD_PTR dwlpBaseAddrHigh; /* 高 32 位地址（x64） */
    DWORD dwLengthLow; /* 低 32 位长度 */
    DWORD dwLengthHigh; /* 高 32 位长度（x64） */
    DWORD dwType; /* 值为 0 表示可用，否则不可用 */
    DWORD dwExtendedAttributes; /* 扩展属性字段 */
} AddressRangeDescriptor, *LPAddressRangeDescriptor, *LPMemoryAddressMap;
\end{minted}

依据上述结构，解析引导加载器中准备的内存地址图，得到虚拟机提供的的物理内存信息如图 \ref{QUERY_MEMORY_INFO}。

\begin{figure}[h!]
  \Centering
  \caption{查询物理内存信息}
  \label{QUERY_MEMORY_INFO}
  \includegraphics[scale=0.8]{build/Paper/Assets/QueryMemoryInfo.png}
\end{figure}

需要指出的是，地址范围描述符中的地址都是实际的物理地址。对照实模式下 1MiB 内存布局（图 \ref{REAL_MODE_1MiB_LAYOUT}）， \lstinline{0x0000'0000} \TO \lstinline{0x0009'FBFF} 刚好对应于 EDBA 之前的 RAM 内存空间（包含了 IVT、BootLoader、页目录、页表等数据结构及操作系统内核）；\lstinline{0x0009'FC00} \TO \lstinline{0x0009'FFFF} 对应于 EDBA；\lstinline{0x000F'0000} \TO \lstinline{0x000F'FFFF} 则是像 BIOS 等固件的映射区域，这与实模式的内存映射是完全一致的；随后是一大块可用的内存空间 \lstinline{0x0010'0000} \TO \lstinline{0x01FD'FFFF}，其大小为 32,374,784 Bytes，共 7,904 个页面，对于本项目实现的操作系统而言已经足够；最后是两片不可用的内存范围。由此可以看出，物理地址并不是连续的，而是若干片离散的连续空间。

\subsubsection{物理内存池的创建}

实模式下的 \MiB{1} 物理内存空间的虚拟地址映射在引导加载器运行时就已经完全确定并且不再变动。
在实模式 \MiB{1} 内存空间外，为本操作系统开辟出 \MiB{16} 可用的原始物理内存空间，在这块内存空间上实现最原始的内存页面分配，每次分配的单位是一个页面的大小。
除了处理器自身具备的对栈内存空间的动态管理能力，此时的操作系统尚不具备任何动态内存管理能力，只能使用一种能够在编译时就完全确定的静态数据结构控制这部分物理内存空间。
位图（Bitmap）足够简单，并且对于每项资源仅需要 1 位存储空间。
\MiB{16} 内存空间中包含了 4096 个页面，数量并不多，以页面为单位使用位图记录这部分资源需要 128 个双字（32 位处理器一次能够处理大小为一个双字的数据）。
通过扫描内存地址图，找到一片连续的 \MiB{16} 物理内存空间，然后初始化位图用于管理这片空间。
在位图中，用 1 来表示页面可分配，0 表示页面已分配。

\subsubsection{物理页面的分配与回收}

Intel 80386 提供了大量能够直接对二进制串进行操作的指令。以 \lstinline{bsr} 指令为例，该指令可自 MSB 开始向 LSB 扫描双字中第一个置位的位，利用该指令可实现物理内存资源的快速分配。

正确初始化内存池后，就可以在需要时申请、分配其中的页面。分配页面时自后向前扫描位图，找到第一个置位的 bit，然后返回该位在整个位图中的偏移量。扫描位图算法也可进一步优化，在操作系统中维护这样一个索引，存放最近一次查询到的空闲位所在的 \lstinline{DWORD}，下一次分配物理页框时直接从该索引指示的 \lstinline{DWORD} 开始向后检索。扫描位图的函数使用纯汇编实现，通过采用 fastcall 调用约定，还可以进一步减少函数调用的时间开销。

回收物理页面的方式类似，通过 \lstinline{bts} 指令使能待回收的位，并将该位存入 \lstinline{EFLAGS.CF} 中。如果发现 \lstinline{EFLAGS.CF} 为 1（即页面并没有被分配过），则返回错误信息给函数调用方。如果没有问题，则计算出该位在哪个 \lstinline{DWORD} 元素中，并将该值返回 , 调用方亦可以根据该返回值调整查询索引。
